﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using WimaxSimulator.Properties;

namespace WimaxSimulator.Wimax
{
    [Serializable]
    public class RelayStation : Station, ISubordinate, ISuperordinate
    {
        public int AvaliableBandwidth { get { return BaseStation.AvaliableBandwidth; } }

        [NonSerialized]
        private BaseStation baseStation;
        public BaseStation BaseStation
        {
            get
            {
                return baseStation;
            }
            set
            {
                baseStation = value;
                foreach (ISubordinate s in Subordinates)
                    s.BaseStation = baseStation;
            }
        }

        [NonSerialized]
        private List<ISubordinate> subordinates = new List<ISubordinate>();
        public List<ISubordinate> Subordinates
        {
            get
            {
                if (subordinates == null)
                    subordinates = new List<ISubordinate>();
                return subordinates;
            }
        }

        [NonSerialized]
        private Dictionary<ISubordinate, Queue<Packet>> downlinkQueue = new Dictionary<ISubordinate, Queue<Packet>>();
        public Dictionary<ISubordinate, Queue<Packet>> DownlinkQueue
        {
            get
            {
                if (downlinkQueue == null)
                    downlinkQueue = new Dictionary<ISubordinate, Queue<Packet>>();
                return downlinkQueue;
            }
        }

        [NonSerialized]
        private Queue<Packet> uplinkQueue = new Queue<Packet>();
        public Queue<Packet> UplinkQueue
        {
            get
            {
                if (uplinkQueue == null)
                    uplinkQueue = new Queue<Packet>();
                return uplinkQueue;
            }
        }

        [NonSerialized]
        private int totalSubordinateMs = 0;
        public int TotalSubordinateMS
        {
            get
            {
                SimulationEngine.Log(Id, "TotalSubordinateMS");
                return totalSubordinateMs + (from s in Subordinates where s is ISuperordinate select ((ISuperordinate)s).TotalSubordinateMS).Sum();
            }
        }

        public int DownlinkBandwidth { get; set; }

        [NonSerialized]
        private ISuperordinate accessStation;
        public ISuperordinate AccessStation { get { return accessStation; } set { accessStation = value; } }

        [NonSerialized]
        private uint homeAddress;
        public uint HomeAddress { get { return homeAddress; } set { homeAddress = value; } }

        [NonSerialized]
        private uint careOfAddress;
        public uint CareOfAddress
        {
            get
            {
                return careOfAddress;
            }
            set
            {
                
                careOfAddress = value;                             

                LastIp = careOfAddress;
                for (int i=0;i<Subordinates.Count;i++) 
                {
                    ISubordinate s =Subordinates[i];
                    if (assignIp(s) == 0)
                    {
                        s.ForceDisconnect();
                        i--;
                    }
                }
                if (HomeAddress == 0) HomeAddress = careOfAddress;
            }
        }

        public int UplinkBandwidth { get; set; }

        [NonSerialized]
        private Cidr ipAddressBlock;
        public Cidr IpAddressBlock
        {
            get
            {
                return ipAddressBlock;
            }
            set
            {
                ipAddressBlock = value;
                CareOfAddress = ipAddressBlock.Ip + 1;
                if (HomeAddress == 0)
                    HomeAddress = CareOfAddress;
            }
        }

        [NonSerialized]
        private List<Cidr> ipAddressAssignment = new List<Cidr>();
        public List<Cidr> IpAddressAssignment
        {
            get
            {
                if (ipAddressAssignment == null)
                    ipAddressAssignment = new List<Cidr>();
                return ipAddressAssignment;
            }
        }

        public int Congestion
        {
            get
            {
                int congestion = 0;
                foreach (Queue<Packet> q in DownlinkQueue.Values)
                {
                    foreach (Packet p in q)
                    {
                        congestion += p.Size;
                    }
                }
                foreach (Packet p in UplinkQueue)
                {
                    congestion += p.Size;
                }
                return congestion;
            }

        }
        public override uint Ip
        {
            get { return CareOfAddress; }
        }
        public RelayStation(int id, int downlinkBw, Point location, int coverageRadius)
            : base(id, location, coverageRadius)
        {
            DownlinkBandwidth = downlinkBw;
            CareOfAddress = 0x00000000;
        }

        public override uint Connect(ISubordinate subordinateStation)
        {
            if (AccessStation != null && AccessStation.AvaliableBandwidth >= subordinateStation.UplinkBandwidth)
            {
                if (subordinateStation is RelayStation && IsMySuper(((ISuperordinate)subordinateStation)))
                    return 0;
                uint ip = base.Connect(subordinateStation);
                if (ip > 0 && subordinateStation is MobileStation)
                    totalSubordinateMs++;
                return ip;
            }
            return 0;
        }

        public void ForceDisconnect()
        {
            while (Subordinates.Count > 0)
            {                
                Subordinates[0].ForceDisconnect();
            }
            AccessStation.Disconnect(this, IpAddressBlock);
            CareOfAddress = 0;
            BaseStation = null;
            AccessStation = null;
        }
        public override void Receive(Packet p)
        {
            if (AccessStation == null)
                return;
            bool dl = p.LastStation == AccessStation;
            p.LastStation = this;
            SimulationEngine.ReceivedData[Id] += p.Size;

            if (dl)
            {
                updateTrafficAnalysis(false, p.Source.Ip, p.Size);
                TotalULReceived += p.Size;
            }
            else
                TotalDLReceived += p.Size;
            // TODO:Check mobile ip
            if ((p.Destination.Ip & IpAddressBlock.SubnetMask) == (CareOfAddress & IpAddressBlock.SubnetMask))
            {
                ISubordinate destQ = (from s in Subordinates
                                      where ~(s.CareOfAddress ^ p.Destination.Ip) == (from sub in Subordinates
                                                                                      select ~(sub.CareOfAddress ^ p.Destination.Ip)).Max<uint>()
                                      select s).SingleOrDefault<ISubordinate>();
                if (destQ != null && (dl || SimulationEngine.RelayingMode))
                {
                    DownlinkQueue[destQ].Enqueue(p);
                    return;
                }
            }
            UplinkQueue.Enqueue(p);
        }

        public override void Send()
        {
            if (AccessStation == null)
                return;
            int bw;
            //Send downlink data
            foreach (ISubordinate so in Subordinates)
            {

                Station s = (Station)so;
                MCS mcs = getMCS(s.Location);
                bw = bandwidthForSubordinate(so) * (int)mcs;

                //if (s is ISuperordinate)
                //    bw = bw * ((ISuperordinate)s).TotalSubordinateMS;
                while (true)
                {
                    if (DownlinkQueue[so].Count == 0)
                        break;
                    Packet p = DownlinkQueue[so].Peek();
                    if (p.Size < bw)
                    {
                        DownlinkQueue[so].Dequeue();
                        bw -= p.Size;
                        TotalDLSent += p.Size;
                        s.Receive(p);
                    }
                    else
                    {
                        if (bw > 0)
                        {
                            Packet ps = new Packet(p.Source, p.Destination, bw);//fragmentation
                            ps.LastStation = this;
                            p.Size -= bw;
                            TotalDLSent += bw;
                            s.Receive(ps);
                        }
                        break;
                    }
                }
            }
            //Send uplink data
            MCS upMcs = getMCS(((Station)AccessStation).Location);
            bw = UplinkBandwidth * (int)upMcs;
            while (true)
            {
                if (UplinkQueue.Count == 0)
                    break;
                Packet p = UplinkQueue.Peek();
                if (p.Size <= bw)
                {
                    UplinkQueue.Dequeue();
                    bw -= p.Size;
                    ((Station)AccessStation).Receive(p);
                    TotalULSent += p.Size;
                    updateTrafficAnalysis(true, p.Destination.Ip, p.Size);
                }
                else
                {
                    if (bw > 0)
                    {
                        p.Size -= bw;
                        ((Station)AccessStation).Receive(new Packet(p.Source, p.Destination, bw));//fragmentation
                        TotalULSent += bw;
                        updateTrafficAnalysis(true, p.Destination.Ip, bw);
                    }
                    break;
                }
            }
        }

        public bool BandwidthRequest(int bandwidth, bool relay)
        {
            if (AccessStation != null && AccessStation.BandwidthRequest(bandwidth, true))
            {
                if (!relay)
                    totalSubordinateMs++;
                UplinkBandwidth += bandwidth;
                return true;
            }
            return false;
        }

        public void PathSelect(List<ISuperordinate> parents)
        {
            if (parents.Count == 0)
                return;
            foreach (ISuperordinate parent in parents)
            {
                if (!SentTrafficAnalysis.ContainsKey(parent))
                    SentTrafficAnalysis.Add(parent, 1);
                if (!ReceivedTrafficAnalysis.ContainsKey(parent))
                    ReceivedTrafficAnalysis.Add(parent, 1);
            }
            ISuperordinate access = (from p in parents
                                     where rate(p) == (from r in parents
                                                       select rate(r)).Max()
                                     select p).FirstOrDefault<ISuperordinate>();
            if (AccessStation != access)
            {
                if (AccessStation != null)
                    SimulationEngine.Log(Id, "Access station\t" + ((Station)AccessStation).Id + "\trate: " + rate(AccessStation) + "\tcandidate\t" + ((Station)access).Id + "\trate: " + rate(access) + "\tavaliable bandwidth" + access.AvaliableBandwidth);
                if (AccessStation != null && rate(AccessStation) * Settings.Default.AccessChangeTreshold > rate(access))
                    return;
                if (AccessStation != null)
                    SimulationEngine.Log(Id, "select diconnect\t" + ((Station)AccessStation).Id + "\trate: " + rate(AccessStation) + "\tconnect\t" + ((Station)access).Id + "\trate: " + rate(access));
                //Retry to connect other stations
                if (!connectToStation(access))
                {
                    SimulationEngine.Log(Id, "cannot connect to\t" + ((Station)access).Id + "\tCurrent access station\t" + AccessStation);
                    parents.Remove(access);
                    if (parents.Count > 0)
                        PathSelect(parents);
                    else
                    {
                        SimulationEngine.Log(Id, "There is no avaliable station to connect\tCurrent access station\t" + AccessStation+" Number of subordinates:"+this.Subordinates.Count);                        
                    }
                }
            }
        }

        private bool connectToStation(ISuperordinate accessStation)
        {
            Cidr oldAddress = IpAddressBlock;
            if (accessStation != null && (accessStation.Connect(this)) > 0)
            {
                MinMCS = (MCS)Math.Min((int)getMCS(((Station)accessStation).Location), (int)accessStation.MinMCS);
                if (AccessStation != null)
                    AccessStation.Disconnect(this, oldAddress);
                this.AccessStation = accessStation;
                return true;
            }
            return false;
        }

        public override string ToString()
        {
            if (IpAddressBlock == null)
                return base.ToString();
            return this.TotalSubordinateMS + " " + UplinkBandwidth;/*IpToStr(CareOfAddress) + "/" + IpAddressBlock.Subnet*/ ;
        }

        public void Disconnect(RelayStation relaySta, Cidr addressBlock)
        {
            UplinkBandwidth -= relaySta.UplinkBandwidth;
            AccessStation.ReturnBandwidth(relaySta.UplinkBandwidth);
            this.DownlinkQueue.Remove(relaySta);

            this.SentTrafficAnalysis.Remove(relaySta);
            ReceivedTrafficAnalysis.Remove(relaySta);
            IpAddressAssignment.Remove(addressBlock);

            Subordinates.Remove(relaySta);
        }

        public override void Disconnect(MobileStation mobileStation)
        {
            base.Disconnect(mobileStation);
            this.UplinkBandwidth -= mobileStation.UplinkBandwidth;
            if (AccessStation != null)
                AccessStation.ReturnBandwidth(mobileStation.UplinkBandwidth);
            this.totalSubordinateMs--;
        }

        public void ReturnBandwidth(int bandwidth)
        {
            UplinkBandwidth -= bandwidth;
            AccessStation.ReturnBandwidth(bandwidth);
        }
        private int bandwidthForSubordinate(ISubordinate s)
        {
            if (DownlinkQueue[s].Count == 0)
                return 0;
            int totalToSend = (from q in DownlinkQueue.Values
                               select q.Count).Sum();
            return DownlinkBandwidth / totalToSend * DownlinkQueue[s].Count;
        }

        #region ISuperordinate Members


        public MCS MinMCS { get; set; }

        #endregion


        public bool IsMySuper(ISuperordinate superOrdinate)
        {
            if (AccessStation == superOrdinate)
                return true;
            if (AccessStation != null)
            {
                return AccessStation.IsMySuper(superOrdinate);
            }
            return false;
        }
        public override void Clear()
        {
            base.Clear();
            totalSubordinateMs = 0;
        }
    }
}
